5. Cache Selection

In some specialised circumstances you may need to programatically select the cache used when a @Cacheable or @CacheFlush annotation is hit. An example might be a multi-tenant application where optimising cache utilisation by caching content for different tenants in their own caches would make sense.

To apply cache selection you need a bean in the Spring context that implements the grails.plugin.springcache.CacheResolver interface. The interface is extremely simple having only one method resolveCacheName(String) which is passed the "base" cache name declared on your annotation and should return the actual cache name to use. You can then add a cacheResolver parameter to @Cacheable and @CacheFlush annotations referencing the bean name of your CacheResolver implementation.

You can override the default CacheResolver used by the plugin by simply redefining the bean springcacheDefaultCacheResolver in resources.groovy .

Example usage

A simple example based on the Multi-Tenant plugin. Cached actions should use a different cache depending on the current tenant.

First we define a CacheResolver implementation that will simply append the current tenant id to the base cache name:

import grails.plugin.springcache.CacheResolver
import org.springframework.web.context.request.RequestContextHolder

class MultiTenantCacheResolver implements CacheResolver {

def tenantResolver // a component of the Multi-Tenant plugin, see that plugin's documentation

String resolveCacheName(String baseName) { def request = RequestContextHolder.requestAttributes.currentRequest def tenantId = tenantResolver.getTenantFromRequest(request) "${baseName}-tenant-${tenantId}" } }

Then we need to wire up our cache resolver in the Spring application context in the grails-app/conf/spring/resources.groovy file and wiring in the dependency on the tenantResolver bean provided by the Multi-Tenant plugin.

multiTenantCacheResolver(MultiTenantCacheResolver) {
	tenantResolver = ref("tenantResolver")
}

Finally we just need to reference the multiTenantCacheResolver bean on any annotations in parts of the code that need to be multi-tenant aware:

@Cacheable(cache = "userCache", cacheResolver = "multiTenantCacheResolver")
def list = {
	[users: User.list()] // under the multi-tenant plugin this will only return user instances for the current tenant
}

@CacheFlush(caches = "userCache", cacheResolver = "multiTenantCacheResolver") def save = { def user = new User(params) // … save the new user, redirect, handle errors, etc. }

If you use cache selection when it's not really appropriate it's very easy to get into problems with cache invalidation and stale data.